#ifndef _TRAP_H_
#define _TRAP_H_

#include "types.h"

#include <stdexcept>

#define MSB (1ull << (XLEN - 1))

enum class Trap : word_t {
    InstructionAddressMisaligned = 0,
    InstructionAccessFault = 1,
    IllegalInstruction = 2,
    Breakpoint = 3,
    LoadAddressMisaligned = 4,
    LoadAccessFault = 5,
    StoreAMOAddressMisaligned = 6,
    StoreAMOAccessFault = 7,
    EnvironmentCallFromUMode = 8,
    EnvironmentCallFromSMode = 9,
    EnvironmentCallFromMMode = 11,
    InstructionPageFault = 12,
    LoadPageFault = 13,
    StoreAMOPageFault = 15,

    UserSoftwareInterrupt = MSB | 0,
    SupervisorSoftwareInterrupt = MSB | 1,
    MachineSoftwareInterrupt = MSB | 3,
    UserTimerInterrupt = MSB | 4,
    SupervisorTimerInterrupt = MSB | 5,
    MachineTimerInterrupt = MSB | 7,
    UserExternalInterrupt = MSB | 8,
    SupervisorExternalInterrupt = MSB | 9,
    MachineExternalInterrupt = MSB | 11,
};

inline bool fatal_trap(Trap& trap) {
    return trap == Trap::InstructionAddressMisaligned ||
           trap == Trap::InstructionAccessFault ||
           trap == Trap::IllegalInstruction ||
           trap == Trap::Breakpoint ||
           trap == Trap::LoadAddressMisaligned ||
           trap == Trap::LoadAccessFault ||
           trap == Trap::StoreAMOAddressMisaligned ||
           trap == Trap::StoreAMOAccessFault ||
           trap == Trap::InstructionPageFault ||
           trap == Trap::LoadPageFault ||
           trap == Trap::StoreAMOPageFault;
}

inline bool is_intr(Trap& trap) {
    return trap == Trap::UserSoftwareInterrupt ||
           trap == Trap::SupervisorSoftwareInterrupt ||
           trap == Trap::MachineSoftwareInterrupt ||
           trap == Trap::UserTimerInterrupt ||
           trap == Trap::SupervisorTimerInterrupt ||
           trap == Trap::MachineTimerInterrupt ||
           trap == Trap::UserExternalInterrupt ||
           trap == Trap::SupervisorExternalInterrupt ||
           trap == Trap::MachineExternalInterrupt;
}

class RVException {
private:
    Trap trap_cause;
    word_t trap_pc;
    word_t trap_val;
public:
    RVException(Trap cause) : trap_cause(cause) {}
    RVException(Trap cause, word_t tpc, word_t tval) : trap_cause(cause), trap_pc(tpc), trap_val(tval) {}
    Trap get_trap_cause() const { return trap_cause; }
    word_t get_trap_pc() const { return trap_pc; }
    word_t get_trap_val() const { return trap_val; }
    const char *what() {
        switch (trap_cause) {
        case Trap::InstructionAddressMisaligned:
            return "Instruction address misaligned";
        case Trap::InstructionAccessFault:
            return "Instruction access fault";
        case Trap::IllegalInstruction:
            return "Illegal instruction";
        case Trap::Breakpoint:
            return "Breakpoint";
        case Trap::LoadAddressMisaligned:
            return "Load address misaligned";
        case Trap::LoadAccessFault:
            return "Load access fault";
        case Trap::StoreAMOAddressMisaligned:
            return "Store/AMO address misaligned";
        case Trap::StoreAMOAccessFault:
            return "Store/AMO access fault";
        case Trap::EnvironmentCallFromUMode:
            return "Environment call from U-mode";
        case Trap::EnvironmentCallFromSMode:
            return "Environment call from S-mode";
        case Trap::EnvironmentCallFromMMode:
            return "Environment call from M-mode";
        case Trap::InstructionPageFault:
            return "Instruction page fault";
        case Trap::LoadPageFault:
            return "Load page fault";
        case Trap::StoreAMOPageFault:
            return "Store/AMO page fault";
        default:
            return "Unknown exception";
        }
    }
};

#endif
